// SPDX-License-Identifier: GPL-2.0

#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

#include <linux/fs.h>
#include <linux/err.h>
#include <linux/errno.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/limits.h>
#include <linux/vmalloc.h>
#include <linux/kernel_read_file.h>

#include "../common/dim_json.h"
#include "../common/dim_utils.h"
#include "dim_core_static_baseline.h"
#include "dim_core_policy.h"
#include "dim_core.h"
#include "dim_core_sig.h"

static const char *sig_ext = ".sig";
static struct dim_baseline_handle *dim_static_baseline;

/* key field in baseline json file */
static const char *baseline_key_products = "products";
static const char *baseline_key_files = "ccFiles";
static const char *baseline_key_patches = "patches";
static const char *baseline_key_filename = "fileName";
static const char *baseline_key_filetype = "fileType";
static const char *baseline_key_patch_files = "files";
static const char *baseline_key_algo_sha256 = "sha256";

/* list for all static baseline file */
static LIST_HEAD(baseline_file_list);
static DEFINE_SPINLOCK(baseline_file_list_lock);
struct baseline_file_node {
	char *path;
	struct list_head list;
};

static int dim_static_baseline_compare(void *data1, void *data2)
{
	return dim_baseline_node_compare((struct dim_baseline_node *)data1,
					 (struct dim_baseline_node *)data2);
}

static void *dim_static_baseline_malloc(unsigned long size)
{
	return kmalloc(size, GFP_KERNEL);
}

static void dim_static_baseline_free(void *data)
{
	kfree(data);
}

static struct dim_baseline_node *
dim_baseline_node_from_file_object(struct dim_baseline_handle *handle,
				   struct dim_json_object *obj,
				   const char *obj_name)
{
	struct dim_baseline_node *node = NULL;
	struct dim_json_object *t_obj = NULL;
	int ret;

	if (obj == NULL || handle == NULL || obj_name == NULL)
		return ERR_PTR(-EINVAL);

	node = dim_baseline_node_create(handle);
	if (IS_ERR(node))
		return node;

	/* set fileName field */
	ret = dim_baseline_node_set_filename(handle, node, obj_name);
	if (ret < 0)
		goto err;

	/* parse and set fileType field */
	t_obj = dim_json_get_object(obj,
		baseline_key_filetype, DIM_JSON_STRING);
	if (t_obj == NULL) {
		ret = -EINVAL;
		goto err;
	}
	ret = dim_baseline_node_set_type_str(handle,
		node, (const char *)t_obj->value);
	if (ret < 0)
		goto err;

	/* parse and set hash algo and data */
	t_obj = dim_json_get_object(obj,
		baseline_key_algo_sha256, DIM_JSON_STRING);
	if (t_obj == NULL) {
		ret = -EINVAL;
		goto err;
	}

	ret = dim_baseline_node_set_hash_algo_str(handle, node, t_obj->key);
	if (ret)
		goto err;

	ret = dim_baseline_node_set_main_hash_data_str(handle, node, (const char *)t_obj->value);
	if (ret)
		goto err;

	return node;
err:
	dim_baseline_node_destroy(handle, node);
	return ERR_PTR(ret);
}

static int
dim_baseline_node_add_iterator_in_files_array(struct dim_json_object *obj,
					      void *data)
{
	struct dim_baseline_handle *handle = (struct dim_baseline_handle *)data;
	struct dim_baseline_node *node = NULL;
	struct dim_json_object *obj_name = NULL;
	char *path_buf = NULL;
	char *r_path = NULL;
	int ret = 0;

	if (handle == NULL || obj == NULL)
		return -EINVAL;

	/* check if the filename is in policy */
	obj_name = dim_json_get_object(obj,
		baseline_key_filename, DIM_JSON_STRING);
	if (obj_name == NULL)
		return 0; /* ignore invalid data */

	if (((char *)obj_name->value)[0] == '/') {
		path_buf = kmalloc(PATH_MAX, GFP_KERNEL);
		if (path_buf == NULL)
			return -ENOMEM;

		r_path = dim_get_absolute_path((char *)obj_name->value,
			path_buf, PATH_MAX);
		if (IS_ERR(r_path)) {
			if (PTR_ERR(r_path) != -ENOENT)
				pr_warn("fail to get absolute path %s: %ld\n",
					(char *)obj_name->value,
					PTR_ERR(r_path));
			r_path = (char *)obj_name->value;
		}
	} else {
		r_path = (char *)obj_name->value;
	}

	if (!dim_policy_has_item(r_path))
		goto out;

	node = dim_baseline_node_from_file_object(handle, obj, r_path);
	if (IS_ERR(node)) {
		pr_warn("fail to create baseline node: %s, reason: %ld\n",
			(const char *)obj_name->value, PTR_ERR(node));
		goto out;
	}

	ret = dim_baseline_add_node(handle, node);
	if (ret < 0) {
		if (node->type != DIM_BASELINE_KERNEL)
			pr_warn("fail to add baseline node: %s, reason: %d\n",
				(const char *)obj_name->value, ret);
		dim_baseline_node_destroy(handle, node);
		goto out;
	}

out:
	if (path_buf != NULL)
		kfree(path_buf);
	return 0;
}

static int
dim_baseline_node_add_iterator_in_patches_array(struct dim_json_object *obj,
						void *data)
{
	struct dim_json_object *obj_files = NULL;
	struct dim_baseline_handle *handle =
		(struct dim_baseline_handle *)data;

	if (handle == NULL || obj == NULL)
		return -EINVAL;

	obj_files = dim_json_get_object(obj,
		baseline_key_patch_files, DIM_JSON_ARRAY);
	if (obj_files != NULL)
		dim_json_iterator_array(obj_files,
			dim_baseline_node_add_iterator_in_files_array, handle);

	return 0;
}

static int
dim_baseline_node_add_iterator_in_products_array(struct dim_json_object *obj,
						 void *data)
{
	struct dim_json_object *obj_files = NULL;
	struct dim_baseline_handle *handle =
		(struct dim_baseline_handle *)data;

	if (handle == NULL || obj == NULL)
		return -EINVAL;

	obj_files = dim_json_get_object(obj,
		baseline_key_files, DIM_JSON_ARRAY);
	if (obj_files != NULL)
		dim_json_iterator_array(obj_files,
			dim_baseline_node_add_iterator_in_files_array,
			handle);

	obj_files = dim_json_get_object(obj,
		baseline_key_patches, DIM_JSON_ARRAY);
	if (obj_files != NULL)
		dim_json_iterator_array(obj_files,
			dim_baseline_node_add_iterator_in_patches_array,
			handle);

	return 0;
}

static int
dim_baseline_nodes_add_from_file(struct dim_baseline_handle *handle,
				 const char *path)
{
	int ret;
	void *buf = NULL;
	void *sbuf = NULL;
	loff_t buf_len = 0;
	loff_t sbuf_len = 0;
	char *spath = NULL;
	struct dim_json_object *obj = NULL;
	struct dim_json_object *obj_products = NULL;

	if (path == NULL || handle == NULL)
		return -EINVAL;

	ret = kernel_read_file_from_path(path, 0, &buf,
		DIM_MAX_DIGEST_FILE_SIZE, NULL, READING_UNKNOWN);
	if (ret < 0) {
		pr_warn("fail to read %s, reason: %d\n", path, ret);
		return 0;
	}

	buf_len = ret;

	spath = kmalloc(strlen(path) + strlen(sig_ext) + 1, GFP_KERNEL);
	if (spath == NULL)
		goto out;

	(void)sprintf(spath, "%s%s", path, sig_ext);

	ret = kernel_read_file_from_path(spath, 0, &sbuf,
		DIM_MAX_CERT_FILE_SIZE, NULL, READING_UNKNOWN);
	if (ret < 0) {
		pr_warn("fail to read %s, reason: %d\n", spath, ret);
		goto out;
	}

	sbuf_len = ret;

	ret = dim_sig_verify(buf, buf_len, sbuf, sbuf_len);
	if (ret < 0) {
		pr_warn("fail to verify signature %s: %d\n", path, ret);
		goto out;
	}

	obj = dim_json_unmarshal(buf, buf_len);
	if (IS_ERR(obj)) {
		ret = PTR_ERR(obj);
		obj = NULL;
		pr_warn("fail to unmarshal json %s: %d\n", path, ret);
		goto out;
	}

	obj_products = dim_json_get_object(obj,
		baseline_key_products, DIM_JSON_ARRAY);
	if (obj_products == NULL) {
		pr_warn("fail to get '%s' array of %s\n",
			baseline_key_products, path);
		goto out;
	}

	dim_json_iterator_array(obj_products,
		dim_baseline_node_add_iterator_in_products_array, handle);

out:
	if (buf != NULL)
		vfree(buf);
	if (sbuf != NULL)
		vfree(sbuf);
	if (spath != NULL)
		kfree(spath);

	dim_json_free(obj);
	return 0; /* return 0 always */
}

static int dim_baseline_fill_dir(struct dir_context *ctx,
				 const char *name,
				 int name_len,
				 loff_t offset,
				 unsigned long long ino,
				 unsigned int d_type)
{
	struct baseline_file_node *node = NULL;

	if (d_type != DT_REG || (!dim_filename_has_ext(name, ".hash")))
		return 0; /* ignore invalid files */

	node = kmalloc(sizeof(struct baseline_file_node), GFP_KERNEL);
	if (node == NULL) {
		pr_warn("fail to alloc memory in baseline iterating\n");
		return 0;
	}

	node->path = dim_path_join(DIM_DIAGEST_ROOT_PATH, name);
	if (IS_ERR(node->path)) {
		pr_warn("fail to handle baseline file %s: %ld\n",
			name, PTR_ERR(node->path));
		kfree(node);
		return 0;
	}

	spin_lock(&baseline_file_list_lock);
	list_add(&node->list, &baseline_file_list);
	spin_unlock(&baseline_file_list_lock);
	return 0;
}

static int dim_baseline_add_nodes_from_root(void)
{
	struct file *p_root;
	int ret;
	struct dir_context dir_ctx = {.actor = dim_baseline_fill_dir};
	struct baseline_file_node *p = NULL;
	struct baseline_file_node *n = NULL;

	p_root = filp_open(DIM_DIAGEST_ROOT_PATH, O_RDONLY | O_DIRECTORY, 0);
	if (IS_ERR(p_root)) {
		ret = PTR_ERR(p_root);
		pr_err("fail to open %s: %d\n", DIM_DIAGEST_ROOT_PATH, ret);
		return ret;
	}

	(void)iterate_dir(p_root, &dir_ctx);

	list_for_each_entry_safe(p, n, &baseline_file_list, list) {
		ret = dim_baseline_nodes_add_from_file(dim_static_baseline,
			p->path);
		if (ret < 0)
			pr_warn("fail to add static baseline from %s: %d\n",
				p->path, ret);

		list_del(&p->list);
		kfree(p->path);
		kfree(p);
	}

	filp_close(p_root, NULL);
	return 0;
}

int dim_static_baseline_build(void)
{
	int ret;

	if (dim_static_baseline != NULL)
		dim_baseline_handle_destroy(dim_static_baseline);

	dim_static_baseline = dim_baseline_handle_create(
		dim_static_baseline_compare, dim_static_baseline_malloc,
		dim_static_baseline_free);
	if (IS_ERR(dim_static_baseline)) {
		ret = PTR_ERR(dim_static_baseline);
		dim_static_baseline = NULL;
		pr_err("fail to init static baseline, reason: %d\n", ret);
		return ret;
	}

	ret = dim_baseline_add_nodes_from_root();
	if (ret != 0) {
		dim_baseline_handle_destroy(dim_static_baseline);
		dim_static_baseline = NULL;
		return ret;
	}

	return 0;
}

void dim_static_baseline_destroy(void)
{
	dim_baseline_handle_destroy(dim_static_baseline);
	dim_static_baseline = NULL;
}

struct dim_baseline_node *
dim_static_baseline_search(char *filename,
			   enum dim_baseline_type type)
{
	struct dim_baseline_node node = {
		.filename = filename,
		.type = type
	};

	return dim_baseline_search_node(dim_static_baseline, &node);
}
